#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tokenizer.h"
#include "list.h"



static int add_token_handler(struct list_t** head, enum token_feedback (*handler)(const char* const, int), enum token_type type)
{
	struct token_handler_t* tmp = (struct token_handler_t*)malloc(sizeof(struct token_handler_t));
	if(!tmp)
		return -1;

	tmp->handler = handler;
	tmp->type = type;
	if(list_add(head, tmp)) {
		free(tmp);
		return -1;
	}
	return 0;
}

static void free_token_handler(void* ptr)
{
	free(ptr);
}

static struct script_holder_t* load_script(const char* const filename)
{
	struct script_holder_t* sh;
	FILE* f;

	if(!(sh = (struct script_holder_t*)malloc(sizeof(struct script_holder_t))))
		return NULL;
	if(!(f = fopen(filename, "rb"))) {
		free(sh);
		return NULL;
	}
	if(fseek(f, 0, SEEK_END)) {
		free(sh);
		fclose(f);
		return NULL;
	}
	if((sh->length = ftell(f)) == -1) {
		free(sh);
		fclose(f);
		return NULL;
	}
	if(!(sh->data = (char*)malloc(sh->length))) {
		free(sh);
		fclose(f);
		return NULL;;
	}
	if(fseek(f, 0L, SEEK_SET)) {
		fclose(f);
		free(sh->data);
		free(sh);
		return NULL;
	}
	if(fread(sh->data, sh->length, 1, f) != 1) {
		fclose(f);
		free(sh->data);
		free(sh);
		return NULL;
	}
	sh->start = 0;
	sh->count = 1;
	return sh;
}

static void free_script(struct script_holder_t* sh)
{
	free(sh->data);
	free(sh);
}

static void init_token_handlers(struct list_t* handler_list)
{
	struct token_handler_t* handler;
	struct list_iterator_t list_iterator = list_get_iterator(handler_list);

	while(handler = list_payload_prev(&list_iterator))
		handler->state = STATE_ACTIVE;
}

static int all_token_handlers_inactive(struct list_t* handler_list)
{
	struct token_handler_t* handler;
	struct list_iterator_t list_iterator = list_get_iterator(handler_list);

	while(handler = list_payload_prev(&list_iterator))
		if(handler->state == STATE_ACTIVE)
			return 0;
	return 1;
}

static int add_token_hit(struct list_t** head, struct token_handler_t* token_handler, int length)
{
	struct token_hit_t* tmp = (struct token_hit_t*)malloc(sizeof(struct token_hit_t));
	if(!tmp)
		return -1;
	tmp->handler = token_handler;
	tmp->length = length;
	if(list_add(head, tmp)) {
		free(tmp);
		return -1;
	}
	return 0;
}


static void free_token_hit(void* ptr)
{
	free(ptr);
}


static int do_pass(struct script_holder_t* sh, struct list_t* handler_list, struct list_t** token_hit_list)
{
	struct token_handler_t* handler;
	struct list_iterator_t list_iterator = list_get_iterator(handler_list);

	while(handler = list_payload_prev(&list_iterator))
		if(handler->state == STATE_ACTIVE)
			switch(handler->handler(&sh->data[sh->start], sh->count)) {
				case FEEDBACK_ERROR:
					handler->state = STATE_INACTIVE;
					break;
				case FEEDBACK_DONE:
					if(add_token_hit(token_hit_list, handler, sh->count))
						return -1;
					handler->state = STATE_INACTIVE;
					break;
				case FEEDBACK_NEEDMORE:
					break;
				case FEEDBACK_DONTCARE:
					if(add_token_hit(token_hit_list, handler, sh->count))
						return -1;
					break;
			}

	return 0;
}

static int add_token(struct list_t** token_list, enum token_type type, char* data, int length)
{
	struct token_t* tmp = (struct token_t*)malloc(sizeof(struct token_t));
	if(!tmp)
		return -1;
	tmp->type = type;
	if(length) {
		int copy_length = (length > MAX_TOKEN_LENGTH - 1)?(MAX_TOKEN_LENGTH - 1):(length);
		memcpy(tmp->string, data, copy_length);
		tmp->string[copy_length] = '\0';
	}
	else
		tmp->string[0] = '\0';
	if(list_add(token_list, tmp)) {
		free(tmp);
		return -1;
	}
	return 0;
}

void free_token(void* ptr)
{
	free(ptr);
}

static int flush_token(struct script_holder_t* sh, struct list_t* token_hit_list, struct list_t** token_list, int* count)
{
	struct token_hit_t* hit;
	struct list_iterator_t list_iterator = list_get_iterator(token_hit_list);

	if(hit = list_payload_prev(&list_iterator)) {
		*count = hit->length;
		if(add_token(token_list, hit->handler->type, &sh->data[sh->start], hit->length))
			return -1;
		return 0;
	}
	else {
		*count = 1;
		if(add_token(token_list, TOKEN_UNKNOWN, &sh->data[sh->start], 1))
			return -1;
		return 0;
	}
}

static enum token_feedback handler_whitespace(const char* const string, int length)
{
	int i;
	for(i = 0; i < length; ++i)
		if(string[i] != ' ' && string[i] != '\t' && string[i] != '\r' && string[i] != '\n')
			return FEEDBACK_ERROR;
	return FEEDBACK_DONTCARE;
}


static enum token_feedback handler_integer(const char* const string, int length)
{
	int i;
	if(string[0] < '1' || string[0] > '9')
		return FEEDBACK_ERROR;
	for(i = 1; i < length; ++i)
		if(string[i] < '0' || string[i] > '9')
			return FEEDBACK_ERROR;
	return FEEDBACK_DONTCARE;
}

static enum token_feedback handler_lbracket(const char* const string, int length)
{
	if(length == 1 && string[0] == '(')
		return FEEDBACK_DONE;
	return FEEDBACK_ERROR;
}

static enum token_feedback handler_rbracket(const char* const string, int length)
{
	if(length == 1 && string[0] == ')')
		return FEEDBACK_DONE;
	return FEEDBACK_ERROR;
}

static enum token_feedback handler_string(const char* const string, int length, const char* const pattern)
{
	int pattern_length = strlen(pattern);

	if(length > pattern_length)
		return FEEDBACK_ERROR;
	if(strncmp(string, pattern, length))
		return FEEDBACK_ERROR;
	if(length < pattern_length)
		return FEEDBACK_NEEDMORE;
	return FEEDBACK_DONE;
}

static enum token_feedback handler_string_int(const char* const string, int length)
{
	return handler_string(string, length, "int");
}

static enum token_feedback handler_string_float(const char* const string, int length)
{
	return handler_string(string, length, "float");
}

static enum token_feedback handler_string_struct(const char* const string, int length)
{
	return handler_string(string, length, "struct");
}

static enum token_feedback handler_string_less(const char* const string, int length)
{
	return handler_string(string, length, "<");
}

static int prepare_handlers(struct list_t** handler_list)
{
	if(add_token_handler(handler_list, handler_whitespace, TOKEN_WHITESPACE))
		return -1;
	if(add_token_handler(handler_list, handler_integer, TOKEN_INT)) {
		list_free(handler_list, free_token_handler);
		return -1;
	}
	if(add_token_handler(handler_list, handler_lbracket, TOKEN_LBRACKET)) {
		list_free(handler_list, free_token_handler);
		return -1;
	}
	if(add_token_handler(handler_list, handler_rbracket, TOKEN_RBRACKET)) {
		list_free(handler_list, free_token_handler);
		return -1;
	}
	if(add_token_handler(handler_list, handler_string_int, TOKEN_STRING_INT)) {
		list_free(handler_list, free_token_handler);
		return -1;
	}
	if(add_token_handler(handler_list, handler_string_float, TOKEN_STRING_FLOAT)) {
		list_free(handler_list, free_token_handler);
		return -1;
	}
	if(add_token_handler(handler_list, handler_string_struct, TOKEN_STRING_STRUCT)) {
		list_free(handler_list, free_token_handler);
		return -1;
	}

	return 0;
}

static void rotate_string(char* string)
{
	int i;
	int length = strlen(string);
	for(i = 0; i < length / 2; ++i) {
		char c = string[i];
		string[i] = string[length - i - 1];
		string[length - i - 1] = c;
	}
}

static void token_list_merge_unknown(struct list_t* token_list)
{
	int pos;
	struct list_t *head, *first_unknown;
	struct token_t* first_unknown_payload;

	if(!token_list)
		return;
	head = token_list;
	first_unknown = NULL;

	do {
		struct token_t* token_list_payload = token_list->payload;
		if(token_list_payload->type == TOKEN_UNKNOWN) {
			if(!first_unknown) {
				pos = 1;
				first_unknown = token_list;
				first_unknown_payload = token_list_payload;
			}
			else {
				struct list_t* todel;
				if(pos < MAX_TOKEN_LENGTH - 1) {
					first_unknown_payload->string[pos] = token_list_payload->string[0];
					first_unknown_payload->string[pos + 1] = '\0';
					++pos;
				}
				todel = token_list;
				token_list->next->prev = token_list->prev;
				token_list->prev->next = token_list->next;
				token_list = token_list->next;
				free(todel->payload);
				free(todel);
			}
		}
		else {
			if(first_unknown)
				rotate_string(first_unknown_payload->string);
			first_unknown = NULL;
		}

		token_list = token_list->prev;
	} while(head != token_list);
	if(first_unknown)
		rotate_string(first_unknown_payload->string);
}





struct list_t* tokenize(const char* const filename)
{
	struct script_holder_t* sh;
	struct list_t* handler_list = NULL;
	struct list_t* token_hit_list = NULL;
	struct list_t* token_list = NULL;

	if(!(sh = load_script(filename)))
		return NULL;

	if(prepare_handlers(&handler_list)) {
		free_script(sh);
		return NULL;
	}
	init_token_handlers(handler_list);

	while(sh->start + sh->count - 1 < sh->length) {
		if(do_pass(sh, handler_list, &token_hit_list)) {
			free_script(sh);
			list_free(&handler_list, free_token_handler);
			list_free(&token_hit_list, free_token_hit);
			list_free(&token_list, free_token);
			return NULL;
		}
		if(all_token_handlers_inactive(handler_list) || sh->start + sh->count == sh->length) {
			int count;
			if(flush_token(sh, token_hit_list, &token_list, &count)) {
				free_script(sh);
				list_free(&handler_list, free_token_handler);
				list_free(&token_hit_list, free_token_hit);
				list_free(&token_list, free_token);
				return NULL;
			}
			list_free(&token_hit_list, free_token_hit);
			init_token_handlers(handler_list);
			sh->start += count;
			sh->count = 1;
			continue;
		}
		++sh->count;
	}
	free_script(sh);
	list_free(&handler_list, free_token_handler);
	list_free(&token_hit_list, free_token_hit);

	token_list_merge_unknown(token_list);
	return token_list;
}
static char* token_to_string(enum token_type token)
{
#if 0
	static char* assoc[TOKEN_MAX] = {"TOKEN_INT", "TOKEN_FLOAT", "TOKEN_LBRACKET", "TOKEN_RBRACKET", "TOKEN_TYPE", "TOKEN_UNKNOWN"};
#endif
	static char* assoc[] = {
				"TOKEN_WHITESPACE", "TOKEN_INT", "TOKEN_FLOAT", "TOKEN_LBRACKET",
				"TOKEN_RBRACKET", "TOKEN_TYPE", "TOKEN_STRING_INT", "TOKEN_STRING_FLOAT", 
				"TOKEN_STRING_STRUCT", "TOKEN_UNKNOWN"
				};
	return assoc[token];
}

void token_list_print(struct list_t* token_list)
{
	struct token_t* tmp;
	struct list_iterator_t list_iterator = list_get_iterator(token_list);

	while(tmp = list_payload_next(&list_iterator)) {
		printf("%s -> \"%s\"\n", token_to_string(tmp->type), tmp->string);
	}
}

void token_list_free(struct list_t** token_list)
{
	list_free(token_list, free_token);
}




